using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using EnvDTE;
using MultiAgentsShared;

namespace MultiAgentsClient.Tools;

//public interface IEditorControl
//{
//    public string GetValueFromExecution(int breakPointLine, string variableName);
//    private void ExecuteTilBreakpoint(int breakPointLine);

//    public void AddBreakPoint(string codeFilePath, int breakPointLineNumber);

//    public void ExecuteInDebugMode();
//}

public class VisualStudioControlToolProvider
{
    private static string vsInstallationPath = "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community";

    private DTE dte;
    private string solutionPath;
    private string codeFilePath;

    private string executionResult;
    private ManualResetEvent buildDoneEvent;

    public VisualStudioControlToolProvider(string solutionPath, string codeFilePath) : base()
    {
        this.solutionPath = solutionPath;
        this.codeFilePath = codeFilePath;
        executionResult = "";
    }

    //[GptFunc("Execute the script in visual studio until the breakpoint set " +
    //    "at line# and get value of variable with name",
    //    typeof(ClientToolSetConfig))]
    public string GetValueFromExecution(
        [GptFuncParam("the line# of the script where I should set breakpoint at")]
        int breakPointLine,
        [GptFuncParam("the name of the variable to get")]
        string variableName)
    {
        var errorMsg = LaunchSolution();
        if (errorMsg != null)
        {
            return errorMsg;
        }

        AddBreakPoint(codeFilePath, breakPointLine);

        errorMsg = Execute();
        if (errorMsg != null)
        {
            return errorMsg;
        }

        return GetValueAndQuitDTE(variableName).Result;
    }

    public string LaunchSolution()
    {
        try
        {
            dte = LaunchVsDte(isPreRelease: false);
            dte.MainWindow.Activate();

            // Open the solution
            dte.Solution.Open(solutionPath);

            return null;
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
    }

    public string Execute()
    {
        try
        {
            ExecuteInDebugMode();
            if (executionResult != null)
            {
                return executionResult;
            }
            return null;
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
    }

    private async Task<string> GetValueAndQuitDTE(string variableName)
    {
        string returnVal = null;
        //Task.Run(async () =>
        //{
        // Use a flag to control the retry mechanism
        bool retry = false;
        int retryCount = 0;
        int maxRetries = 10; // You can adjust the maximum number of retries
        int delay = 500; // Delay in milliseconds between retries

        do
        {
            try
            {
                // Wait for the debugger to hit the breakpoint
                if (dte.Debugger.CurrentMode != dbgDebugMode.dbgBreakMode)
                {
                    retry = true;
                    continue;
                }

                Expression variableValue = dte.Debugger.GetExpression(variableName, true, 1000);
                if (variableValue.IsValidValue)
                {
                    returnVal = variableValue.Value;
                    Debug.LogLine($"HellO: {returnVal}");
                }
                else
                {
                    Debug.LogLine($"Could not evaluate the value of {variableName}");
                }

                // Break the loop if successful
                retry = false;
            }
            catch (COMException ex) when (ex.HResult == unchecked((int)0x8001010A)) // RPC_E_SERVERCALL_RETRYLATER
            {
                // Log the retry attempt
                Debug.LogLine("COMException caught (application busy), retrying...");

                // Implement retry logic
                if (retryCount < maxRetries)
                {
                    retry = true;
                    retryCount++;
                    //await Task.Delay(delay);

                    System.Threading.Thread.Sleep(delay);
                }
                else
                {
                    // Log and potentially throw an exception if max retries reached
                    Debug.LogLine("Maximum retries reached, operation failed.");
                    throw;
                }
            }
        } while (retry);

        dte.Quit();
        //});

        return returnVal;
    }

    public void AddBreakPoint(string codeFilePath, int breakPointLineNumber)
    {
        // Open the file
        EnvDTE.Window codeWindow = dte.ItemOperations.OpenFile(codeFilePath, EnvDTE.Constants.vsViewKindTextView);
        dte.Debugger.Breakpoints.Add(File: codeFilePath, Line: breakPointLineNumber);
    }

    public void ExecuteInDebugMode()
    {
        dte.Events.BuildEvents.OnBuildProjConfigDone += OnBuildProjConfigDone;

        // Run the project in debug mode
        dte.ExecuteCommand("Debug.Start");
    }

    private void OnBuildProjConfigDone(string Project, string ProjectConfig, string Platform, string SolutionConfig, bool Success)
    {
        if (!Success)
        {
            executionResult = $"Build Failed for {Project}";
        }
    }

    public void GetVariable(string variableName)
    {
        // Wait for the debugger to hit the breakpoint
        while (dte.Debugger.CurrentMode != dbgDebugMode.dbgBreakMode)
        {
            System.Threading.Thread.Sleep(100);
        }

        // Get the value of a variable at line 12
        Expression variableValue = dte.Debugger.GetExpression(variableName, true, 1000);
        if (variableValue.IsValidValue)
        {
            Debug.LogLine($"Value of {variableName}: {variableValue.Value}");
        }
        else
        {
            Debug.LogError($"Could not evaluate the value of {variableName}");
        }

        // Continue execution
        dte.Debugger.Go();
    }

    #region Helpers

    private static EnvDTE.DTE LaunchVsDte(bool isPreRelease)
    {
        string executablePath = Path.Combine(vsInstallationPath, @"Common7\IDE\devenv.exe");
        System.Diagnostics.Process vsProcess = System.Diagnostics.Process.Start(executablePath);
        string runningObjectDisplayName = $"VisualStudio.DTE.17.0:{vsProcess.Id}"; //DTE.17 is for vs2022

        IEnumerable<string> runningObjectDisplayNames = null;
        object runningObject;
        for (int i = 0; i < 60; i++)
        {
            try
            {
                runningObject = GetRunningObject(runningObjectDisplayName, out runningObjectDisplayNames);
            }
            catch
            {
                runningObject = null;
            }

            if (runningObject != null)
            {
                return (EnvDTE.DTE)runningObject;
            }

            System.Threading.Thread.Sleep(millisecondsTimeout: 1000);
        }

        throw new TimeoutException($"Failed to retrieve DTE object. Current running objects: {string.Join(";", runningObjectDisplayNames)}");
    }

    private static object GetRunningObject(string displayName, out IEnumerable<string> runningObjectDisplayNames)
    {
        IBindCtx bindContext = null;
        NativeMethods.CreateBindCtx(0, out bindContext);

        IRunningObjectTable runningObjectTable = null;
        bindContext.GetRunningObjectTable(out runningObjectTable);

        IEnumMoniker monikerEnumerator = null;
        runningObjectTable.EnumRunning(out monikerEnumerator);

        object runningObject = null;
        List<string> runningObjectDisplayNameList = new List<string>();
        IMoniker[] monikers = new IMoniker[1];
        IntPtr numberFetched = IntPtr.Zero;
        while (monikerEnumerator.Next(1, monikers, numberFetched) == 0)
        {
            IMoniker moniker = monikers[0];

            string objectDisplayName = null;
            try
            {
                moniker.GetDisplayName(bindContext, null, out objectDisplayName);
            }
            catch (UnauthorizedAccessException)
            {
                // Some ROT objects require elevated permissions.
            }

            if (!string.IsNullOrWhiteSpace(objectDisplayName))
            {
                runningObjectDisplayNameList.Add(objectDisplayName);
                if (objectDisplayName.EndsWith(displayName, StringComparison.Ordinal))
                {
                    runningObjectTable.GetObject(moniker, out runningObject);
                    if (runningObject == null)
                    {
                        throw new InvalidOperationException($"Failed to get running object with display name {displayName}");
                    }
                }
            }
        }

        runningObjectDisplayNames = runningObjectDisplayNameList;
        return runningObject;
    }


    private static class NativeMethods
    {
        [DllImport("ole32.dll")]
        public static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
    }

    #endregion
}
